<?php

namespace App\Console\Commands\Settlement;

use App\Exceptions\InsufficientTenantBalanceException;
use App\Models\Settlement;
use App\Models\SubSettlement;
use App\Models\Tenant;
use App\Models\Transaction;
use App\Services\Bank\BankServiceFactory;
use App\Services\Settlement\SettlementBatchPayService;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class ProcessSettlementBatchPayCommand extends Command
{
    protected $signature = 'settlement:batch-pay
        {settlementId : 结算单ID}
        {settlementNumber : 结算单编号}
        {tenantId : 租户ID}
        {initiatorId : 发起人ID}
        {--bank=BankOfHangzhou : 银行驱动名称}
        {--max-query-attempts=10 : 查询轮询最大次数}
        {--query-interval=30 : 查询轮询间隔秒数}';

    protected $description = '对指定结算单执行批量发放流程';

    public function handle(SettlementBatchPayService $service): int
    {
        $settlementId = (string) $this->argument('settlementId');
        $settlementNumber = (string) $this->argument('settlementNumber');
        $tenantId = (int) $this->argument('tenantId');
        $initiatorId = (int) $this->argument('initiatorId');
        $bankName = (string) $this->option('bank');
        $maxQueryAttempts = (int) $this->option('max-query-attempts');
        $queryIntervalSeconds = (int) $this->option('query-interval');

        $settlement = Settlement::where('id', $settlementId)
            ->where('tenant_id', $tenantId)
            ->first();

        if (! $settlement) {
            $message = sprintf('结算单不存在或租户不匹配：%s', $settlementNumber);
            Log::warning('[BatchPay] ' . $message, [
                'settlement_id' => $settlementId,
                'settlement_number' => $settlementNumber,
                'tenant_id' => $tenantId,
            ]);
            $this->error($message);

            return Command::FAILURE;
        }

        Log::info('[BatchPay] 启动批量发放任务', [
            'settlement_id' => $settlement->id,
            'settlement_number' => $settlement->settlement_number,
            'tenant_id' => $tenantId,
        ]);

        Settlement::whereKey($settlement->id)->update([
            'status' => Settlement::STATUS_DISTRIBUTING,
            'distribute_started_at' => $settlement->distribute_started_at ?? Carbon::now(),
        ]);

        $pendingQuery = SubSettlement::where('settlement_id', $settlement->id)
            ->where('status', SubSettlement::STATUS_PENDING_SUBMIT)
            ->orderBy('id');

        $totalRequiredBalance = (clone $pendingQuery)->sum('required_balance');
        if ($totalRequiredBalance <= 0) {
            Log::info('[BatchPay] 没有待提交的子结算单，跳过处理', [
                'settlement_id' => $settlement->id,
                'settlement_number' => $settlement->settlement_number,
            ]);
            $this->warn('没有待提交的子结算单，任务结束');

            return Command::SUCCESS;
        }

        $tenantBalance = (float) (Tenant::whereKey($tenantId)->value('account_balance') ?? 0);
        $totalRequiredBalanceFormatted = number_format($totalRequiredBalance, 2, '.', '');
        if (bccomp((string) $tenantBalance, $totalRequiredBalanceFormatted, 2) < 0) {
            Log::info('[BatchPay] 租户余额不足，批量发放终止', [
                'settlement_number' => $settlementNumber,
                'tenant_id' => $tenantId,
                'tenant_balance' => $tenantBalance,
                'required_amount' => $totalRequiredBalanceFormatted,
            ]);
            $this->warn(sprintf('租户余额不足：当前余额 %s，需要 %s', number_format($tenantBalance, 2, '.', ''), $totalRequiredBalanceFormatted));

            return Command::FAILURE;
        }

        $bankService = BankServiceFactory::make($bankName);
        $batchSubmitSize = (int) config("bank.banks.{$bankName}.batch_submit_size", 50);
        $batchQuerySize = (int) config("bank.banks.{$bankName}.batch_query_size", 50);

        $chunkIndex = 0;
        $chunkContexts = [];

        while (true) {
            $chunk = (clone $pendingQuery)->limit($batchSubmitSize)->get();
            if ($chunk->isEmpty()) {
                break;
            }

            try {
                $reservation = $service->reserveBalanceForChunk(
                    $settlement,
                    $chunk,
                    $tenantId,
                    $initiatorId,
                    $bankName
                );
            } catch (InsufficientTenantBalanceException $exception) {
                Log::error('[BatchPay] 预扣失败：余额不足', [
                    'settlement_number' => $settlementNumber,
                    'tenant_id' => $tenantId,
                    'message' => $exception->getMessage(),
                ]);
                $this->error($exception->getMessage());

                break;
            }

            $erpBatchNo = $this->generateErpBatchNo($settlementNumber, ++$chunkIndex);
            $wageTitle = $settlement->settlement_name ?: $settlementNumber;
            $busiType = config("bank.banks.{$bankName}.defaults.default_busi_type", '1');

            $payDetails = $this->buildPayDetails($chunk);

            Log::info('[BatchPay] 提交批量代发', [
                'settlement_number' => $settlementNumber,
                'tenant_id' => $tenantId,
                'erp_batch_no' => $erpBatchNo,
                'sub_settlement_ids' => $chunk->pluck('id'),
                'amount' => $reservation['amount'],
                'bank' => $bankName,
            ]);

            $response = $bankService->batchPay($erpBatchNo, $wageTitle, $busiType, $payDetails);

            Log::info('[BatchPay] 批量代发响应', [
                'settlement_number' => $settlementNumber,
                'tenant_id' => $tenantId,
                'erp_batch_no' => $erpBatchNo,
                'response' => $response,
            ]);

            if (empty($response['success'])) {
                $reason = $response['ret_msg'] ?? '批量代发失败';
                $service->markChunkFailed($settlement, $chunk, $reservation['transaction'], $reason);
                Log::error('[BatchPay] 批量代发失败', [
                    'settlement_number' => $settlementNumber,
                    'tenant_id' => $tenantId,
                    'erp_batch_no' => $erpBatchNo,
                    'reason' => $reason,
                ]);
                $this->error(sprintf('批量代发失败：%s', $reason));

                continue;
            }

            $wageNo = (string) Arr::get($response, 'map_data.wageNo', $erpBatchNo);
            $service->markChunkDistributing($chunk, $wageNo, $response['ret_msg'] ?? null);

            $reservationTransaction = $reservation['transaction'];
            $metadata = $reservationTransaction->metadata ?? [];
            $metadata['bank_batch_id'] = $wageNo;
            $metadata['erp_batch_no'] = $erpBatchNo;
            $reservationTransaction->metadata = $metadata;
            $reservationTransaction->save();

            $chunkContexts[$wageNo] = [
                'erp_batch_no' => $erpBatchNo,
                'transaction_id' => $reservationTransaction->id,
                'sub_settlement_ids' => $chunk->pluck('id')->all(),
            ];

            $pendingQuery = SubSettlement::where('settlement_id', $settlement->id)
                ->where('status', SubSettlement::STATUS_PENDING_SUBMIT)
                ->orderBy('id');
        }

        if (empty($chunkContexts)) {
            Log::info('[BatchPay] 未提交任何批次，结束任务', [
                'settlement_number' => $settlementNumber,
            ]);
            $this->warn('未提交任何批次，任务结束');

            return Command::SUCCESS;
        }

        $this->pollDistributingStatus(
            $service,
            $settlement,
            $bankService,
            $chunkContexts,
            $batchQuerySize,
            $settlementNumber,
            $tenantId,
            $maxQueryAttempts,
            $queryIntervalSeconds
        );

        $this->refreshSettlementSummary($settlement);
        $this->info('批量发放任务完成');

        return Command::SUCCESS;
    }

    private function buildPayDetails(Collection $subSettlements): array
    {
        return $subSettlements->map(function (SubSettlement $item) {
            $amount = number_format((float) $item->commission, 2, '.', '');

            return [
                'empNo' => (string) $item->id,
                'userName' => $item->freelancer_name,
                'bankAcc' => $item->freelancer_bank_account,
                'amt' => $amount,
                'purpose' => $item->payment_remarks ?: '结算发放',
                'payeeAccNo' => $item->freelancer_bank_account,
                'payeeName' => $item->freelancer_name,
                'amount' => $amount,
                'remark' => $item->payment_remarks ?: '结算发放',
            ];
        })->all();
    }

    private function generateErpBatchNo(string $settlementNumber, int $index): string
    {
        return sprintf(
            '%s-%s-%s',
            Str::upper(Str::slug($settlementNumber, '')),
            Carbon::now()->format('His'),
            str_pad((string) $index, 2, '0', STR_PAD_LEFT)
        );
    }

    private function pollDistributingStatus(
        SettlementBatchPayService $service,
        Settlement $settlement,
        $bankService,
        array $chunkContexts,
        int $batchQuerySize,
        string $settlementNumber,
        int $tenantId,
        int $maxAttempts,
        int $intervalSeconds
    ): void {
        $attempt = 0;

        while ($attempt < $maxAttempts) {
            $attempt++;

            $distributing = SubSettlement::where('settlement_id', $settlement->id)
                ->where('status', SubSettlement::STATUS_DISTRIBUTING)
                ->get();

            if ($distributing->isEmpty()) {
                break;
            }

            $grouped = $distributing->groupBy('bank_batch_id');

            foreach ($grouped as $bankBatchId => $items) {
                if (! isset($chunkContexts[$bankBatchId])) {
                    continue;
                }

                $context = $chunkContexts[$bankBatchId];
                $transaction = Transaction::find($context['transaction_id']);
                if (! $transaction) {
                    continue;
                }

                foreach ($items->chunk(max($batchQuerySize, 1)) as $chunk) {
                    Log::info('[BatchPay] 查询批量代发结果', [
                        'settlement_number' => $settlementNumber,
                        'tenant_id' => $tenantId,
                        'bank_batch_id' => $bankBatchId,
                        'erp_batch_no' => $context['erp_batch_no'],
                        'batch_size' => $chunk->count(),
                    ]);

                    $response = $bankService->queryPayResult($bankBatchId);

                    Log::info('[BatchPay] 批量代发查询响应', [
                        'settlement_number' => $settlementNumber,
                        'tenant_id' => $tenantId,
                        'bank_batch_id' => $bankBatchId,
                        'response' => $response,
                    ]);

                    $this->applyQueryResult(
                        $service,
                        $settlement,
                        $chunk,
                        $transaction,
                        $response,
                        (string) $bankBatchId,
                        $tenantId,
                        $settlementNumber
                    );
                }
            }

            sleep($intervalSeconds);
        }
    }

    private function applyQueryResult(
        SettlementBatchPayService $service,
        Settlement $settlement,
        Collection $items,
        Transaction $transaction,
        array $response,
        string $bankBatchId,
        int $tenantId,
        string $settlementNumber
    ): void {
        if (empty($response['success'])) {
            return;
        }

        $loopData = collect($response['loop_data'] ?? []);
        if ($loopData->isEmpty()) {
            return;
        }

        $now = Carbon::now();
        $completed = collect();
        $failed = collect();

        foreach ($items as $item) {
            $row = $loopData->first(function ($detail) use ($item) {
                return ($detail['empNo'] ?? null) === (string) $item->id;
            });

            if (! $row) {
                continue;
            }

            $status = strtoupper((string) ($row['status'] ?? $row['bizStatus'] ?? $row['payStatus'] ?? ''));
            $message = $row['statusMsg'] ?? $row['remark'] ?? $row['msg'] ?? $response['ret_msg'] ?? '批量代发处理中';

            if (in_array($status, ['S', 'SUCCESS', '0', '02', 'SUCC', 'Y'], true)) {
                SubSettlement::whereKey($item->id)->update([
                    'status' => SubSettlement::STATUS_COMPLETED,
                    'status_description' => $message,
                    'distributed_at' => $now,
                ]);
                $completed->push($item);
            } elseif (in_array($status, ['F', 'FAIL', '2', '03', 'N'], true)) {
                SubSettlement::whereKey($item->id)->update([
                    'status' => SubSettlement::STATUS_DISTRIBUTE_FAILED,
                    'status_description' => $message,
                    'bank_batch_id' => null,
                    'distributed_at' => $now,
                ]);
                $failed->push($item);
            }
        }

        if ($failed->isNotEmpty()) {
            $reason = '银行返回发放失败';
            $service->refundFailedSubSettlements($settlement, $failed, $reason, $this->argument('initiatorId'));
            Log::info('[BatchPay] 子结算失败已退款', [
                'settlement_number' => $settlementNumber,
                'tenant_id' => $tenantId,
                'bank_batch_id' => $bankBatchId,
                'failed_ids' => $failed->pluck('id'),
            ]);
        }

        $remaining = SubSettlement::where('settlement_id', $settlement->id)
            ->where('bank_batch_id', $bankBatchId)
            ->where('status', SubSettlement::STATUS_DISTRIBUTING)
            ->exists();

        if (! $remaining) {
            $service->markTransactionCompleted($transaction);
        }
    }

    private function refreshSettlementSummary(Settlement $settlement): void
    {
        $settlement->refresh();

        $totalCount = SubSettlement::where('settlement_id', $settlement->id)->count();
        $successCount = SubSettlement::where('settlement_id', $settlement->id)
            ->where('status', SubSettlement::STATUS_COMPLETED)
            ->count();
        $failedCount = SubSettlement::where('settlement_id', $settlement->id)
            ->whereIn('status', [
                SubSettlement::STATUS_DISTRIBUTE_FAILED,
                SubSettlement::STATUS_CANCELLED,
            ])->count();

        $successAmount = SubSettlement::where('settlement_id', $settlement->id)
            ->where('status', SubSettlement::STATUS_COMPLETED)
            ->sum('commission');

        $settlementStatus = $settlement->status;
        if ($totalCount > 0 && $successCount === $totalCount) {
            $settlementStatus = Settlement::STATUS_COMPLETED;
        } elseif ($failedCount > 0 && $successCount > 0) {
            $settlementStatus = Settlement::STATUS_PARTIAL_FAILED;
        } elseif ($failedCount > 0 && $successCount === 0) {
            $settlementStatus = Settlement::STATUS_DISTRIBUTE_FAILED;
        } else {
            $settlementStatus = Settlement::STATUS_DISTRIBUTING;
        }

        Settlement::whereKey($settlement->id)->update([
            'status' => $settlementStatus,
            'success_amount' => number_format($successAmount, 2, '.', ''),
            'success_count' => $successCount,
            'total_amount' => SubSettlement::where('settlement_id', $settlement->id)->sum('commission'),
            'total_count' => $totalCount,
            'distribute_started_at' => $settlement->distribute_started_at ?? Carbon::now(),
            'completed_at' => $settlementStatus === Settlement::STATUS_COMPLETED ? Carbon::now() : $settlement->completed_at,
        ]);
    }
}
